<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
	/**
	 * CodeIgniter
	 *
	 * An open source application development framework for PHP 4.3.2 or newer
	 *
	 * @package		CodeIgniter
	 * @author		ExpressionEngine Dev Team
	 * @copyright	Copyright (c) 2008 - 2009, EllisLab, Inc.
	 * @license		http://codeigniter.com/user_guide/license.html
	 * @link		http://codeigniter.com
	 * @since		Version 1.0
	 * @filesource
	 */
	// ------------------------------------------------------------------------
	
	/**
	 * @package		CodeIgniter
	 * @subpackage	Libraries
	 * @author		AnhDT
	 */
	 
	require_once APP_LIBRARY_PATH . 'html/tag/interfaces/st_interface_tag.php';
	require_once APP_LIBRARY_PATH . 'html/tag/events/st_tag_event_handler.php';
	
	abstract class st_abstract_tag {
		
		protected static $_instance    = null;
		protected $_attributes         = array(
										 'tag'   => '',
										 'id'    => '',
										 'name'  => '',
										 'class' => array()
									    );
		protected $_events             = array(
											'onclick'     => null,
											'ondbclick'   => null,
											'onkeydown'   => null,
											'onkeypress'  => null,
											'onkeyup'     => null,
											'onmousedown' => null,
											'onmouseover' => null,
											'onmouseout'  => null,
											'onmouseup'   => null,
										);
		protected $_configs            = array(
											// Có phải là loại thẻ chứa được các thẻ khác ko
											'isContainer'          => false,
											// Có phải là thẻ tự đóng hay không
											'autoClosed'           => false,
											// Có hiển thị các thuộc tính có gtrị rỗng hay ko
											'showEmptyAttribute'   => false,
											// Có hiển thị thuộc tính giá trị bên trong thẻ
											'showValueInside'      => false,
											// Có thể thay đổi tên thẻ sau khi đã đặt hay ko
											'canChangeTag'         => false,
											// Có xuất ra xử lý sự kiện javascript hay ko
											'outputEvent'          => true,
											// Danh sách các sự kiện javascript ko được xuất
											'blackListEvents'      => array(),
											// Danh sách các thuộc tính ko được xuất
											'blackListAttributes'  => array(),
										);
		/**Các thẻ con được lồng trong thẻ cha*/
		protected $_children           = array();
		/**Mô hình Singleton Pattern*/
		public function getInstance() {
			if (self::$_instance === null) {
				self::$_instance = new self;
			}
			return self::$_instance;
		}
		
		/**Hiển thị tag dưới dạng text/html*/
		public function show() {
			$this->__toHtml();
			return $this;
		}
		
		/**Xử lý callback của bộ xử lý sự kiện trên thẻ*/
		public function activeEventMethods() {
			// Nếu không tồn tại bộ xử lý sự kiện thì thoát ra ngoài
			if (!$this->hasEvtHandlers()) {
				return $this;
			}
			$_handlers = $this->getEvtHandlersRegistered();
			if (count($_handlers) > 0) {
				foreach ($_handlers as $_handler) {
					// Kích hoạt các bộ xử lý sự kiện tương ứng
					$_handler->activeEventMethods();	
				}	
			}
			return $this;
		}
		
		/**Kiểm tra thẻ có bộ xử lý sự kiện nào không*/
		public function hasEvtHandlers() {
			if (count($this->_events) > 0) {
				foreach ($this->_events as $_evt => $_handler) {
					if (!empty($_handler)) {
						return true;	
					}	
				}
			}
			return false;
		}
		
		/**Lấy về nội dung html của tag*/
		public function getOutputHtml() {
			// Kiểm tra thẻ có hợp lệ hay không
			if (!$this->__isValidTag()) {
				return false;	
			}
			$_output = '<' . $this->getAttribute('tag') . ' ';
			foreach ($this->_attributes as $_attribute => $_value) {
				/**
				* Nếu thuộc tính nằm trong dãy các thuộc tính bị vô hiệu hóa thì bỏ qua
				*/
				if (in_array($_attribute, $this->_configs['blackListAttributes'])) {
					continue;	
				}
				/**
				* Nếu có thiết lập không xuất ra những thuộc tính có giá
				* trị rỗng thì bỏ qua việc xuất ra thuộc tính này.
				*/
				if (!$this->getConfig('showEmptyAttribute')) {
					if (empty($_value)) {
						continue;	
					}	
				}
				/**
				* Nếu có thiết lập xuất ra thuộc tính `value` ở
				* bên trong thẻ chứ không phải dưới dạng thuộc tính của thẻ
				* thì bỏ qua việc xuất ra thuộc tính này. Khi gần đóng thẻ
				* giá trị của thẻ sẽ được xuất ra sau
				*/
				if ($this->getConfig('showValueInside')) {
					if ($_attribute == 'value') {
						continue;	
					}
				}
				switch ($_attribute) {
					/**Tránh việc xuất ra thuộc tính tên thẻ, vì nó đã được hiển thị*/
					case 'tag':
							continue;
						break;
					/**Đối với thuộc tính lớp có nhiều giá trị, chuyển đổi array -> string*/	
					case 'class':
							$_value = implode(' ', $_value);
							$_output .= ' ' . $_attribute . '="' . $_value . '"';	
						break;
					default:
						$_output .= ' ' . $_attribute . '="' . $_value . '"';		
					break;
				}
			}
			/**Xử lý các sự kiện trên thẻ nếu có thiết lập xuất ra các sự kiện*/
			if ($this->getConfig('outputEvent')) {
				foreach ($this->_events as $_event => $_handler) {
					/**Nếu sự kiện không nằm trong dãy các sự kiện bị vô hiệu hóa thì xuất ra*/
					if (!in_array($_event, $this->_configs['blackListEvents'])) {
						if ($_handler instanceof st_tag_event_handler) {
							$_output .= ' ' . $_event . '=\'' . $_handler->toJsString() . '\'';		
						}
					}
				}
			}
			/**Xử lý đóng thẻ, dựa vào thiết lập là thẻ tự đóng hay không*/
			if ($this->getConfig('autoClosed')) {
				/**Nếu là thẻ tự đóng, đối với thuộc tính value dù được
				* thiết lập hiển thị bên trong thẻ, cũng phải hiển thị
				* dưới dạng một thuộc tính
				*/
				if ($this->getConfig('showValueInside')) {
					if ($this->hasAttribute('value')) {
						$_output .= ' value="'.$this->getAttribute('value').'"';			
					}	
				}
				$_output .= ' />';
			} else {
				$_output .= '>';
				if ($this->getConfig('showValueInside')) {
					if ($this->hasAttribute('value')) {
						$_output .= $this->getAttribute('value');			
					}	
				}
				/**In nội dung các thẻ con*/
				if ($this->hasChildren()) {
					$_children = $this->getChildren();
					/**Lấy về nội dung theo đệ quy*/
					foreach ($_children as $_child) {
						$_output .= $_child->getOutputHtml();
					}
				}
				$_output .= '</'.$this->getTag().'>';
			}
			/**Render lại định dạng xuất ra của thẻ*/
			$_output = preg_replace('/\s{2,}/', ' ', $_output);
			$_output = preg_replace('/(>)(<)(\/*)/', "$1\n$2$3", $_output);
			return $_output;
		}
		/**
		* Kiểm tra thẻ có hợp lệ hay không
		* Điều kiện: ít nhất phải có thuộc tính id, và tag Name
		* @return boolean
		*/
		protected function __isValidTag() {
			foreach (array('tag', 'id') as $_key) {
				if (!array_key_exists($_key, $this->_attributes)) {
					return false;
				}
			}
			return true;
		}
		
		/** Tập hợp các sự kiện khi khởi tạo thẻ */
		protected function __init($_attributes = array()) {
			$this->__evtBeforeInit();
			$this->__evtInit($_attributes);
			$this->__evtAfterInit();
			return $this;
		}
		/** Tập hợp các sự kiện khi hiện ra thẻ */
		protected function __toHtml() {
			$this->__evtBeforeToHtml();
			$this->__evtToHtml();
			$this->__evtAfterToHtml();
			return $this;
		}
		/** Sự kiện khi bắt đầu khởi tạo thẻ*/
		protected function __evtInit($_attributes = array()) {
			if (empty($_attributes)) {
				return false;
			}
			/**Nếu là dạng chuỗi thì khởi tạo dưới định dạng:
			* tên thẻ/giá trị id khởi tạo
			* hoặc tên_thẻ/{hàm_khởi_tạo_giá_trị_id}
			*/
			if (is_string($_attributes)) {
				if (count(explode('/', $_attributes)) < 2) {
					return false;				  
				}
				list($this->_attributes['tag'], $this->_attributes['id']) = explode('/', $_attributes);
				/** Nếu giá trị Id sử dụng phương thức phát sinh thì
				* đánh dấu vị trí cần lưu tên phương thức thực thi
				* thực thi phương thức và lấy kết quả trả về thiết lập là giá trị id.
				*/
				if (preg_match('/\{(.*)\(.*\)\}/', $this->_attributes['id'], $_matched)) {
					$this->_attributes['id'] = $this->$_matched[1]();
				}
				if (isset($this->_attributes['tag'])) {
					$this->_attributes['id'] = $this->_attributes['tag'] . '_' . $this->_attributes['id'];
				}
			} elseif (is_array($_attributes)) {
				$this->setAttributes($_attributes);
			}
			return $this;
		}
		/** Sự kiện trước khi khởi tạo thẻ*/
		protected function __evtBeforeInit() {
			return $this;	
		}
		/** Sự kiện sau khi khởi tạo thẻ*/
		protected function __evtAfterInit() {
			return $this;	
		}
		/** Sự kiện trước khi hiện lên thẻ*/
		protected function __evtBeforeToHtml() {
			return $this;	
		}
		/** Sự kiện khi hiện lên thẻ*/
		protected function __evtToHtml() {
			// Kích hoạt các xử lý sự kiện ở phần server
			$this->activeEventMethods();
			// Lấy nội dung trả về bên client: html
			$_output = $this->getOutputHtml();
			// Xuất ra kết quả
			echo $_output;
			return $this;
		}
		/** Sự kiện sau khi hiện lên thẻ*/
		protected function __evtAfterToHtml() {
			return $this;	
		}
		/** Lấy về giá trị của các thuộc tính trong thẻ*/
		public function getAttributes() {
			return (array)$this->_attributes;	
		}
		/**Thiết lập giá trị của các thuộc tính trong thẻ*/
		public function setAttributes($_attributes = array()) {
			if (empty($_attributes)) {
				return false;	
			}	
			if (is_string($_attributes)) {
				$_attributes = json_decode($_attributes, true);
			}
			/**Những thuộc tính cần phải thiết lập là id và tag,
			* nếu những thuộc tính này không có trong danh sách
			* tham số thì check lại danh sách thuộc tính của lớp
			* đã tồn tại giá trị của các thuộc tính này chưa, nếu
			* chưa thì báo lỗi
			*/
			$_attributes_must_have = array('id', 'tag');
			foreach ($_attributes_must_have as $_attribute) {
				if (!array_key_exists($_attribute, $_attributes)) {
					if (!isset($this->_attributes[$_attribute])) {
						throw new Exception("Attribute $_attribute must in array of attributes");	
					}
				}	
			}
			foreach ($_attributes as $_attributeName => $_value) {
				$this->setAttribute($_attributeName, $_value, true);
			}
			return $this;
		}
		/**Thêm lớp cho thẻ*/
		public function addClass($_className = '', $_replaceClass = '', $_replace = true) {
			if (empty($_className) || !is_string($_className)) {
				return false;	
			}
			/**Nếu thực hiện thay thế lớp khi thêm vào*/
			if ($_replace) {
				/**Kiểm tra đối số tên lớp được thay thế nếu tồn tại thì gỡ lớp đó ra*/
				if (!empty($_replaceClass)) {
					if (isset($this->_attributes['class'][$_replaceClass])) {
						unset($this->_attributes['class'][$_replaceClass]);	
					}	
				}	
			}
			/**Thêm lớp mới vào*/
			$this->_attributes['class'][$_className] = $_className;
			return $this;
		}
		
		/**Gỡ lớp ra*/
		public function removeClass($_className = '') {
			if (!$this->hasClass($_className)) {
				return false;	
			}
			unset($this->_attributes['class'][$_className]);
			return $this;
		}
		
		/**Nếu có tồn tại thì gỡ lớp còn không thì thêm lớp*/ 
		public function toogleClass($_className = '') {
			if (!$this->removeClass($_className)) {
				$this->addClass($_className);	
			}
			return $this;
		}
		
		/**Kiểm tra lớp có tồn tại hay không*/ 
		public function hasClass($_className = '') {
			if (empty($_className) || !is_string($_className)) {
				return false;	
			}
			return (isset($this->_attributes['class'][$_className]));
		}
		
		/**Lấy về giá trị của một thuộc tính*/
		public function getAttribute($_attributeName = '') {
			if (!is_string($_attributeName) || empty($_attributeName)) {
				return false;	
			}	
			return $this->_attributes[$_attributeName];
		}
		
		/**Thiết lập các lớp*/
		public function setClasses($_classes = '') {
			if (empty($_classes)) {
				return false;	
			}
			if (is_string($_classes)) {
				$_classes = json_decode($_classes, true);
			}
			$this->_attributes['class'] = & $_classes;
			return $this;
		}
		
		/**Đặt giá trị cho 1 thuộc tính cụ thể, nếu ignoreCheck đặt là true, thì bỏ qua
		* công đoạn kiểm tra là thuộc tính này đã tồn tại trong thẻ hay chưa, nếu được 
		* kiểm tra, và nếu thuộc tính chưa tồn tại thì giá trị không thể được thiết lập.
		*/
		public function setAttribute($_attributeName = '', $_value, $_ignoreCheck = true) {
			if (!is_string($_attributeName) || empty($_attributeName) || empty($_value)) {
				return false;	
			}
			// Check lại ràng buộc về thuộc tính
			$this->checkAttributeConfig($_attributeName);
			if (!$_ignoreCheck) {
				if (!array_key_exists($_attributeName, $this->_attributes)) {
					return false;
				}	
			}
			$this->_attributes[$_attributeName] = $_value;
			return $this;
		}
		
		/**Check lại sự thiết lập ràng buộc của attribute
		*/
		public function checkAttributeConfig($_attribute = '') {
			/**Kiểm tra có thể thay đổi tên thẻ sau khi đã thiết lập giá
			trị cho thuộc tính hay không, nếu không thì thoát ra
			*/
			if ($_attribute == 'tag') {
				if (!empty($this->_attributes['tag'])) {
					if (!$this->getConfig('canChangeTag')) {
						throw new Exception('Attribute "Tag" is set: "'.$this->getTag().'", you do not permission to change it, may be can do if you change config class {canChangeTag: true}');
					}
				}
			}
			return $this;
		}
		
		/**Kiểm tra có tồn tại thuộc tính này không
		*/
		public function hasAttribute($_attribute = '') {
			if (!is_string($_attribute) || empty($_attribute) || array_key_exists($_attribute, $this->_attributes)) {
				return false;	
			}	
			return false;
		}
		/**Thiết lập bộ xử lý sự kiện cho sư kiện tương ứng*/
		public function setEvtHandler($_event = '', $_handler = NULL) {
			if (empty($_event) || !is_string($_event) || !array_key_exists($_event, $this->_events)) {
				return false;	
			}
			if (NULL === $_handler || !$_handler instanceof st_tag_event_handler) {
				return false;	
			}
			$_handler->setEvent($_event);
			$_handler->setObjectBinding($this);
			$this->_events[$_event] = & $_handler;
			return $this;
		}
		
		/**Lấy về bộ xử lý sự kiện cho sư kiện tương ứng*/
		public function & getEvtHandler($_event = '') {
			if (empty($_event) || !is_string($_event) || !array_key_exists($_event, $this->_events)) {
				return false;	
			}
			$_handler = & $this->_events[$_event];
			return $_handler;
		}
		
		/**Lấy danh sách các bộ xử lý sự kiện của đối tượng*/
		public function & getEvtHandlers() {
			$_evts = & $this->_events;	
			return $_evts;
		}
		
		/**Lấy các bộ xử lý sự kiện đã được kích hoạt*/
		public function getEvtHandlersActivated() {
			if (!$this->hasEvtHandlers()) {
				return false;	
			}
			$_evtActived = array();
			foreach ($this->_events as $_evt => $_handler) {
				if ($_handler instanceof st_tag_event_handler && $_handler->isActive()) {
					$_evtActived[] = $_handler;	
				}	
			}
			return (count($_evtActived) > 0) ? $_evtActived : false;
		}
		
		/**Lấy các bộ xử lý sự kiện đã được đămg ký*/
		public function getEvtHandlersRegistered() {
			if (!$this->hasEvtHandlers()) {
					return false;	
			}
			$_evtRegistered = array();
			foreach ($this->_events as $_evt => $_handler) {
				if ($_handler instanceof st_tag_event_handler) {
					$_evtRegistered[] = $_handler;	
				}	
			}
			return (count($_evtRegistered) > 0) ? $_evtRegistered : false;
		}
		
		/**Lấy về dữ liệu config*/
		public function & getConfigData() {
			$_configs = & $this->configs;
			return $_configs;
		}
		
		/**Thiết lập dữ liệu config*/
		public function & setConfigData($_configs = array(), $_overWritten = true) {
			if (empty($_configs)) {
				return false;	
			}
			if (is_string($_configs)) {
				$_configs = json_decode($_configs, true);
			}
			foreach ($_configs as $_key => $_value) {
				if ($_overWritten) {
					$this->setConfig($_key, $_value, true);	
				} else {
					$this->setConfig($_key, $_value, false);	
				}	
			}
			return $this;
		}
		
		/**Lấy về dữ liệu config theo key*/
		public function getConfig($_configKey) {
			if (empty($_configKey) || !is_string($_configKey)) {
				return false;	
			}
			return $this->_configs[$_configKey];
		}
		
		/**Thiết lập dữ liệu config theo key*/
		public function setConfig($_configKey = '', $_value = '', $_ignoreCheck = true) {
			if (empty($_configKey)) {
				return false;	
			}
			if (!$_ignoreCheck) {
				if (isset($this->_configs[$_configKey])) {
					return false;	
				}	
			}
			$this->_configs[$_configKey] = $_value;
			return $this;
		}
		
		/**Xóa dữ liệu config*/
		public function removeConfig($_configKey = '') {
			if (empty($_configKey) || !is_string($_configKey) || !isset($this->_configs[$_configKey])) {
				return false;	
			}
			unset($this->_configs[$_configKey]);
			return $this;
		}
		/**Reset lại cấu hình của thẻ*/
		public function resetConfigs() {
			$this->_configs = array();
			return $this;
		}
		/**Reset lại thuộc tính của thẻ*/
		public function resetAttributes() {
			$this->_attributes = array();
			return $this;
		}
		/**Reset lại các bộ xử lý sự kiện của thẻ*/
		public function resetEvtHandlers() {
			$this->_events = array();
			return $this;
		}
		/**Reset lại toàn bộ thông tin của thẻ*/
		public function __reset() {
			$this->resetConfigs();
			$this->resetAttributes();
			$this->resetEvtHandlers();
			$this->clearChildren();
			return $this;
		}
		
		/**Thêm thẻ con*/
		public function addChild($_tag = null) {
			// Nếu thẻ không phải là loại thẻ chứa được thì vô hiệu hóa phương thức
			if (empty($_tag) || !$_tag->__isValidTag() ||!$this->getConfig('isContainer')) {
				return false;	
			}
			$this->_children[$_tag->getId()] = $_tag;
			return $this;
		}
		
		/**Bỏ thẻ con theo Id*/
		public function removeChild($_tagId = '') {
			if (!is_string($_tagId) || empty($_tagId)) {
				return false;	
			}	
			if ($this->hasChild($_tagId)) {
				unset($this->_children[$_tagId]);	
			}
			return $this;
		}
		/**Nếu đã tồn tại thẻ con thì gỡ nó ra, ngược lại thì thêm thẻ con vào*/
		public function toogleChild($_child = null) {
			if (null === $_child || !$_child instanceof st_abstract_class) {
				return false;	
			}	
			if ($this->hasChild($_child)) {
				$this->removeChild($_child);	
			} else {
				$this->addChild($_child);	
			}
			return $this;
		}
		
		/**Kiểm tra thẻ con đã tồn tại chưa
		* @param: string | st_abstract_tag
		* @return: boolean
		*/
		public function hasChild($_tag = '') {
			if (empty($_tag)) {
				return false;	
			}	
			// Kiểm tra bằng id
			if (is_string($_tag)) {
				if (!isset($this->_children[$_tag]))	{
					return false;	
				}
			} elseif ($_tag instanceof st_tag_abstract) {
				// Kiểm tra qua lớp đối tượng
				if (!$_tag->__isValidTag()) {
					return false;	
				} else {
					return $this->hasChild($_tag->getId());	
				}	
			}
			return true;
		}
		
		/**Kiểm tra thẻ có con hay không
		*/
		public function hasChildren() {
			return (!count($this->_children) == 0);
		}
		
		/**Lấy về các thẻ con của thẻ hiện thời
		*/
		public function & getChildren() {
			$_children = & $this->_children;
			return $_children;
		}
		
		/**Lấy về các thẻ con của thẻ hiện thời
		*/
		public function clearChildren() {
			$this->_children = array();
			return $this;
		}
		
		/**
		* Tự động phát sinh ra giá trị ID
		*/
		public function autoGenId($_method = 'sha1') {
			if (function_exists($_method)) {
				$_id = sha1(time());	
			} elseif (function_exists('md5')) {
				$_id = md5(time());	
			} else {
				$_id = time();	
			}
			return $_id;
		}
		
		/**
		* Xử lý các phương thức căn bản như set, get
		* set<TênAttribute>(giá trị)
		* get<TênAttribute>()
		*/
		function __call($_method, $_args) {
			$_method_type = substr($_method, 0, 3);
			$_key         = strtolower(substr($_method, 3));
			unset($_method);
			switch ($_method_type) {
				case 'get':
					if (isset($this->_attributes[$_key])) {
						return $this->getAttribute($_key);	 
					}
					return false;
					break;
				case 'set':
					if (isset($args[0])) {
						$this->setAttribute($_key, $args[0], true);
					}
					return $this;
					break;
				default:
					return false;
					break;
			}
		}
	}
?>